package io.github.iogogogo.commons.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.CollectionUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Created by tao.zeng on 2024/11/7.
 */
@Slf4j
public class SuperBeanUtils extends BeanUtils {

    /**
     * 转换对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param source           源对象
     * @param targetSupplier   目标对象供应方
     * @param ignoreProperties 忽略属性
     * @return 目标对象 t
     */
    public static <S, T> T copyProps(S source, Supplier<T> targetSupplier, final String... ignoreProperties) {
        return copyProps(source, targetSupplier, null, ignoreProperties);
    }

    /**
     * 转换对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param source           源对象
     * @param targetSupplier   目标对象供应方
     * @param callback         回调方法
     * @param ignoreProperties 忽略属性
     * @return 目标对象 t
     */
    public static <S, T> T copyProps(S source, Supplier<T> targetSupplier, ConvertCallback<S, T> callback, final String... ignoreProperties) {
        if (Objects.isNull(source) || Objects.isNull(targetSupplier)) return null;
        T target = targetSupplier.get();
        BeanUtils.copyProperties(source, target, ignoreProperties);
        if (callback != null) callback.onCallback(source, target);
        return target;
    }

    /**
     * 转换集合对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param sources          源对象list
     * @param targetSupplier   目标对象供应方
     * @param ignoreProperties 忽略属性
     * @return 目标对象list list
     */
    public static <S, T> List<T> copyList(List<S> sources, Supplier<T> targetSupplier, final String... ignoreProperties) {
        return copyList(sources, targetSupplier, null, ignoreProperties);
    }

    /**
     * 转换集合对象
     *
     * @param <S>              源对象类型
     * @param <T>              目标对象类型
     * @param sources          源对象list
     * @param targetSupplier   目标对象供应方
     * @param callback         回调方法
     * @param ignoreProperties 忽略属性
     * @return 目标对象list list
     */
    public static <S, T> List<T> copyList(List<S> sources, Supplier<T> targetSupplier, ConvertCallback<S, T> callback, final String... ignoreProperties) {
        if (CollectionUtils.isEmpty(sources) || Objects.isNull(targetSupplier)) return null;
        return sources.stream().map(source -> {
            T target = targetSupplier.get();
            BeanUtils.copyProperties(source, target, ignoreProperties);
            if (callback != null) callback.onCallback(source, target);
            return target;
        }).collect(Collectors.toList());
    }

    /**
     * 回调接口
     *
     * @param <S> 源对象类型
     * @param <T> 目标对象类型
     */
    @FunctionalInterface
    public interface ConvertCallback<S, T> {
        /**
         * On callback.
         *
         * @param source the source
         * @param target the target
         */
        void onCallback(S source, T target);
    }

    /**
     * Trans bean 2 map map.
     * <p>
     * bean 转 map 对象
     *
     * @param obj              源bean对象
     * @param ignoreProperties 忽略属性
     * @return bean转map结果 map
     */
    public static Map<String, Object> beanToMapForJavaAPI(Object obj, String... ignoreProperties) {
        if (Objects.isNull(obj)) return null;
        Map<String, Object> map = new HashMap<>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            Set<String> collect = Stream.of(ignoreProperties).collect(Collectors.toSet());
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                // 过滤class属性
                if (!key.equals("class")) {
                    // 忽略不需要映射的属性
                    if (!CollectionUtils.isEmpty(collect) && collect.contains(key)) continue;
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(obj);
                    map.put(key, value);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return map;
    }

    /**
     * Trans bean 2 map map.
     * <p>
     * bean 转 map 对象
     *
     * @param bean              源bean对象
     * @param ignoreProperties 忽略属性
     * @return bean转map结果 map
     */
    public static <T> Map<String, Object> beanToMapForSpringAPI(T bean, String... ignoreProperties) {
        Map<String, Object> map = new HashMap<>();
        if (Objects.nonNull(bean)) {
            BeanMap beanMap = BeanMap.create(bean);
            Set<String> collect = Stream.of(ignoreProperties).collect(Collectors.toSet());
            for (Object key : beanMap.keySet()) {
                String k = String.valueOf(key);
                if (!CollectionUtils.isEmpty(collect) && collect.contains(k)) continue;
                map.put(k, beanMap.get(key));
            }
        }
        return map;
    }

    /**
     * 将Map对象转换为指定类型的JavaBean对象
     *
     * @param map   包含JavaBean属性的Map对象
     * @param clazz 指定的JavaBean类型
     * @param <T>   泛型参数，表示JavaBean的类型
     * @return 转换后的JavaBean对象，如果转换失败则返回null
     * <p>
     * 此方法的工作原理是首先根据给定的类类型创建一个JavaBean实例，
     * 然后使用BeanMap将Map中的键值对映射到JavaBean的属性上，
     * 如果映射成功，则返回填充了数据的JavaBean对象；
     * 如果在创建JavaBean实例时发生BeanInstantiationException异常，则记录错误日志并返回null
     */
    public static <T> T mapToBean(Map<String, Object> map, Class<T> clazz) {
        try {
            // 创建指定类型的JavaBean实例
            T bean = instantiateClass(clazz);
            // 创建BeanMap对象，用于将JavaBean对象与Map对象进行映射
            BeanMap beanMap = BeanMap.create(bean);
            // 将Map中的键值对映射到JavaBean的属性上
            beanMap.putAll(map);
            // 返回填充了数据的JavaBean对象
            return bean;
        } catch (BeanInstantiationException e) {
            log.error(e.getMessage(), e);
        }
        // 如果发生异常，返回null
        return null;
    }

    /**
     * 行转列
     * <p>
     * 该方法用于将一个bean对象集合按照bean的属性进行行转列的操作，即原本每个bean对象为一行数据，
     * 转换后，每个属性为一列数据，所有对象的同一属性组成一个列表，这些列表再组成一个大的列表。
     * 这种转换在处理某些类型的数据展示或分析时非常有用。
     *
     * @param <T>   the type parameter 泛型参数，表示源bean的类型
     * @param clazz 源bean类型，即我们想要转换成列的bean类
     * @param list  bean类型集合，即原始的行数据集合
     * @return 行转列结果 list 一个包含多个列表的列表，每个子列表代表原始所有对象的一个属性的值集合
     */
    public static <T> List<List<Object>> lineToColumn(Class<T> clazz, List<T> list) {
        // 获取源bean类型的所有属性
        Field[] fields = clazz.getDeclaredFields();
        // 创建一个列表集合，用于存放每个属性对应的值列表
        List<List<Object>> result = IntStream.range(0, fields.length)
                .mapToObj(x -> new ArrayList<>())
                .collect(Collectors.toList());
        // 遍历原始列表中的每个对象，将每个对象的每个属性值添加到对应属性的列表中
        list.forEach(x -> {
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                // 设置属性可访问，以绕过访问修饰符
                field.setAccessible(true);
                try {
                    // 获取对象的属性值并添加到对应列表中
                    result.get(i).add(field.get(x));
                } catch (IllegalAccessException e) {
                    // 如果访问属性值时发生错误，记录错误信息
                    log.error(e.getMessage(), e);
                }
            }
        });
        // 返回行转列后的结果
        return result;
    }
}
